﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;

namespace NMF.Optimizations.Linq
{
    class SelectManySource<TItem, TElement, TTarget> : SelectingSource<TItem, TElement, TTarget>, IObservableSource<TTarget>, ISourceObserver<TItem>
    {
        public SelectManySource(IObservableSource<TItem> source, Func<TItem, IEnumerable<TElement>> func, Func<TItem, TElement, TTarget> selector) : base(source, selector)
        {
            if (func == null) throw new ArgumentNullException("func");

            Func = func;

            source.Subscribe(this);

            foreach (var item in source)
            {
                var collection = func(item) as INotifyCollectionChanged;
                if (collection != null)
                {
                    collection.CollectionChanged += GetObserver(item).ProcessCollectionChanges;
                }
            }
        }

        private Dictionary<TItem, ItemObserver> observers = new Dictionary<TItem, ItemObserver>();
        private class ItemObserver
        {
            public TItem Item { get; set; }
            public SelectManySource<TItem, TElement, TTarget> Parent { get; set; }

            public void ProcessCollectionChanges(object sender, NotifyCollectionChangedEventArgs e)
            {
                Parent.ProcessCollectionChanges(Item, e);
            }
        }

        private ItemObserver GetObserver(TItem item)
        {
            ItemObserver observer;
            if (!observers.TryGetValue(item, out observer))
            {
                observer = new ItemObserver()
                {
                    Item = item,
                    Parent = this
                };
                observers.Add(item, observer);
            }
            return observer;
        }

        void ISourceObserver<TItem>.OnDeletedItem(TItem item)
        {
            var collection = Func(item);
            foreach (var element in collection)
            {
                OnDeletedItem(SelectAndRemove(item, element));
            }
            var notifier = collection as INotifyCollectionChanged;
            if (notifier != null)
            {
                notifier.CollectionChanged -= GetObserver(item).ProcessCollectionChanges;
            }
        }

        void ISourceObserver<TItem>.OnUpdatedItem(TItem item)
        {
            foreach (var element in Func(item))
            {
                var oldSelection = Select(item, element);
                var newSelection = Selector(item, element);

                if (object.Equals(oldSelection, newSelection))
                {
                    OnUpdatedItem(oldSelection);
                }
                else
                {
                    OnDeletedItem(oldSelection);
                    OnNewItem(newSelection);
                }
            }
        }

        void ISourceObserver<TItem>.OnNewItem(TItem item)
        {
            var collection = Func(item);
            foreach (var element in collection)
            {
                OnNewItem(Select(item, element));
            }
            var notifier = collection as INotifyCollectionChanged;
            if (notifier != null)
            {
                notifier.CollectionChanged += GetObserver(item).ProcessCollectionChanges;
            }
        }

        void ISourceObserver<TItem>.OnReset()
        {
            OnReset();
        }

        protected override void OnReset()
        {
            observers.Clear();
            base.OnReset();
        }

        private void ProcessCollectionChanges(TItem item, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (TElement element in e.NewItems)
                    {
                        OnNewItem(Select(item, element));
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (TElement element in e.OldItems)
                    {
                        OnDeletedItem(SelectAndRemove(item, element));
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    foreach (TElement element in e.NewItems)
                    {
                        OnNewItem(Select(item, element));
                    }
                    foreach (TElement element in e.OldItems)
                    {
                        OnDeletedItem(SelectAndRemove(item, element));
                    }
                    break;
                case NotifyCollectionChangedAction.Reset:
                    OnReset();
                    break;
                default:
                    break;
            }
        }

        public Func<TItem, IEnumerable<TElement>> Func { get; private set; }

        public override IEnumerator<TTarget> GetEnumerator()
        {
            return ((IEnumerable<TItem>)Source).SelectMany(Func, Select).GetEnumerator();
        }
    }
}
